#include <linux/module.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/font.h>
#include <linux/mutex.h>
#include <linux/videodev2.h>
#include <linux/kthread.h>
#include <linux/freezer.h>
#include <media/videobuf2-vmalloc.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-fh.h>
#include <media/v4l2-event.h>
#include <media/v4l2-common.h>

/**********************************************************************************************
 V4L2 IOCTL PROCESS:
     video_device.ioctl -> video_device.fops.ioctl(video_ioctl2) -> video_device.ioctl_ops
**********************************************************************************************/

#define DRIVE_NAME "virt-v4l2-drv"

#define VIDEO_MAX_W         1920
#define VIDEO_MAX_H         1080
#define MAX_VB2_BUF_SIZE    16 /*16MB*/

struct video_device *vvd_video_device;
struct v4l2_device   vvd_v4l2_device;
struct vb2_queue     vb2_q;
struct task_struct *kthread;
struct list_head   active;

struct vvd_buffer {
	/* common v4l buffer stuff -- must be first */
	struct vb2_buffer	vb;
	struct list_head	list;
};

static struct v4l2_format vvd_format;

static void vvd_release(struct video_device *vdev)
{
}

static int vvd_vidioc_querycap(struct file *file, void *priv, struct v4l2_capability *cap)
{
	strcpy(cap->driver, DRIVE_NAME);
	strcpy(cap->card,   DRIVE_NAME);
	cap->version = 0x1;
	cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;

	return 0;
}

static int vvd_vidioc_enum_fmt_vid_cap(struct file *file, void *priv, struct v4l2_fmtdesc *f)
{
	if (f->index >= 1) {
		printk("%s just support 1 format, input index: %d\n", DRIVE_NAME, f->index);
		return -EINVAL;
	}

	strcpy(f->description, "4:2:2, packed, YUYV");
	f->pixelformat = V4L2_PIX_FMT_YUYV;

	return 0;
}

static int vvd_vidioc_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f)
{
	unsigned int maxw, maxh;

	if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV) {
		printk("%s:%d vvd just support V4L2_PIX_FMT_YUYV pixel fmt\n", __func__, __LINE__);
		return -EINVAL;
	}

	maxw = VIDEO_MAX_W;
	maxh = VIDEO_MAX_H;

	v4l_bound_align_image(&(f->fmt.pix.width), 48, maxw, 2, \
			      &(f->fmt.pix.height), 32, maxh, 0, 0);

	f->fmt.pix.bytesperline = (f->fmt.pix.width * 16) >> 3;
	f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;

	return 0;
}

static int vvd_vidioc_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f)
{
	int ret = vvd_vidioc_try_fmt_vid_cap(file, priv, f);
	if (ret < 0) {
		return ret;
	}

	memcpy(&vvd_format, f, sizeof(vvd_format));

	return ret;
}

static int vvd_vidioc_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f)
{
	memcpy(f, &vvd_format, sizeof(vvd_format));
	return 0;
}

static int vvd_vb2_queue_setup(struct vb2_queue *vq, const struct v4l2_format *fmt,
				unsigned int *nbuffers, unsigned int *nplanes,
				unsigned int sizes[], void *alloc_ctxs[])
{
	unsigned long size;

	if (fmt)
		size = fmt->fmt.pix.sizeimage;
	else
		size = VIDEO_MAX_W * VIDEO_MAX_H * 16 / 8;

	if (size == 0) {
		printk("%s:%d sizeimage should not be 0!\n", __func__, __LINE__);
		return -EINVAL;
	}

	if (*nbuffers == 0) {
		*nbuffers = 3;
	}

	while((size * *nbuffers) > (MAX_VB2_BUF_SIZE * 1024 * 1024)) {
		printk("---- size: %ld, buffer cnt: %d ----\n", size, *nbuffers);
		(*nbuffers)--;
	}

	*nplanes = 1;
	sizes[0] = size;

	return 0;
}

static int vvd_vb2_buf_init(struct vb2_buffer *vb)
{
	printk("~~~~~~~~ %s:%d ~~~~~~~~\n", __func__, __LINE__);
	return 0;
}

static int vvd_vb2_buf_queue(struct vb2_buffer *vb)
{
	printk("~~~~~~~~ %s:%d vb: %p ~~~~~~~~\n", __func__, __LINE__, vb);
	struct vvd_buffer *buf = container_of(vb, struct vvd_buffer, vb);
	list_add_tail(&buf->list, &active);
	return 0;
}

static int vvd_vb2_buf_finish(struct vb2_buffer *vb)
{
	printk("~~~~~~~~ %s:%d ~~~~~~~~\n", __func__, __LINE__);
	return 0;
}

static int vvd_thread(void *data)
{
	struct vvd_buffer *buf;

	while (1) {
		__udelay(30*1000);
		if (list_empty(&active)) {
			return;
		}

		buf = list_entry(active.next, struct vvd_buffer, list);
		list_del(&buf->list);
		v4l2_get_timestamp(&buf->vb.v4l2_buf.timestamp);
		vb2_buffer_done(&buf->vb, VB2_BUF_STATE_DONE);
	}
}

static int vvd_vb2_start_streaming(struct vb2_queue *vq, unsigned int count)
{
	printk("~~~~~~~~ %s:%d ~~~~~~~~\n", __func__, __LINE__);
	kthread = kthread_run(vvd_thread, NULL, "virt-v4l2-drv");
	return 0;
}

static const struct v4l2_file_operations vvd_fops = {
	.owner = THIS_MODULE,
	.ioctl = video_ioctl2, /* V4L2 ioctl handler */
};

static const struct v4l2_ioctl_ops vvd_ioctl_ops = {
	.vidioc_querycap = vvd_vidioc_querycap,                        /* 表示这是一个摄像头设备 */
	.vidioc_enum_fmt_vid_cap = vvd_vidioc_enum_fmt_vid_cap,        /* 列举支持哪种格式 */
	.vidioc_try_fmt_vid_cap = vvd_vidioc_try_fmt_vid_cap,          /* 测试驱动是否支持某种格式 */
	.vidioc_s_fmt_vid_cap = vvd_vidioc_s_fmt_vid_cap,              /* 设置摄像头使用的格式 */
	.vidioc_g_fmt_vid_cap = vvd_vidioc_g_fmt_vid_cap,              /* 返回当前所使用的格式 */
	.vidioc_reqbufs       = vb2_ioctl_reqbufs,
	.vidioc_create_bufs   = vb2_ioctl_create_bufs,
	.vidioc_prepare_buf   = vb2_ioctl_prepare_buf,
	.vidioc_querybuf      = vb2_ioctl_querybuf,
	.vidioc_qbuf          = vb2_ioctl_qbuf,
	.vidioc_dqbuf         = vb2_ioctl_dqbuf,
	.vidioc_streamon      = vb2_ioctl_streamon,
};

static const struct vb2_ops vvd_video_qops = {
	.queue_setup = vvd_vb2_queue_setup,
	.buf_init = vvd_vb2_buf_init,
	.buf_queue= vvd_vb2_buf_queue,
	.start_streaming = vvd_vb2_start_streaming,
	.buf_finish = vvd_vb2_buf_finish,
};

static int __init vvd_init(void)
{
	int err;

	vvd_video_device = video_device_alloc();

	strlcpy(vvd_v4l2_device.name, DRIVE_NAME, sizeof(vvd_v4l2_device.name));
	err = v4l2_device_register(NULL, &vvd_v4l2_device);
	if (err < 0) {
		printk("%s:%d v4l2_device_register error!\n", __func__, __LINE__);
		goto v4l2_device_register_error;
	}

	vvd_video_device->v4l2_dev = &vvd_v4l2_device;
	vvd_video_device->release  = vvd_release;
	vvd_video_device->fops     = &vvd_fops;
	vvd_video_device->ioctl_ops= &vvd_ioctl_ops;

	vb2_q.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	vb2_q.io_modes = VB2_MMAP | VB2_READ | VB2_USERPTR | VB2_DMABUF;
	vb2_q.drv_priv = &vb2_q;
	vb2_q.buf_struct_size = sizeof(struct vvd_buffer);
	vb2_q.ops = &vvd_video_qops;
	vb2_q.mem_ops = &vb2_vmalloc_memops;
	vb2_q.timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;

	err = vb2_queue_init(&vb2_q);
	if (err) {
		printk("%s:%d vb2_queue_init error!\n", __func__, __LINE__);
		goto vb2_queue_init_error;
	}

	vvd_video_device->queue = &vb2_q;
	vvd_video_device->debug = 0;
	err = video_register_device(vvd_video_device, VFL_TYPE_GRABBER, -1);
	if (err < 0) {
		printk("%s:%d video_register_device error!\n", __func__, __LINE__);
		goto video_register_device_error;
	}

	INIT_LIST_HEAD(&active);

	return err;

vb2_queue_init_error:
video_register_device_error:
	v4l2_device_unregister(&vvd_v4l2_device);
v4l2_device_register_error:
	kfree(vvd_video_device);

	return -1;
}

static void __exit vvd_exit(void)
{
	video_unregister_device(vvd_video_device);
	video_device_release(vvd_video_device);
}

module_init(vvd_init);
module_exit(vvd_exit);

MODULE_LICENSE("GPL");
